﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using ZeBobo5.LibraryHelper.Core.Sql;

namespace ZeBobo5.LibraryHelper.Orm
{
    public class OrmDataElementList<A> : IListSource, IEnumerable<A>
        where A : OrmDataElementBase<A>, new()
    {
        #region Delegates

        public delegate void OrmDataElementSaveErrorEventHandler(object sender, OrmDataElementSaveErrorEventArgs<A> arg);

        public delegate void OrmDataOperatingEventHandler(object sender, OrmDataElementOperatingEventArgs<A> arg);

        #endregion

        private readonly List<A> lst = new List<A>();
        private OrmDataElementListState _state = OrmDataElementListState.NoChanged;
        internal string _strConnectionName;

        internal OrmDataElementList()
        {
        }

        public A this[int index]
        {
            get
            {
                if (index >= 0 && index < Count)
                {
                    int cpt = 0;
                    foreach (A _t in this)
                    {
                        if (cpt == index)
                        {
                            return _t;
                        }
                        if (_t.State != OrmDataElementState.Deleted && _t.State != OrmDataElementState.ToDelete)
                        {
                            cpt++;
                        }
                    }
                }
                throw new ArgumentOutOfRangeException();
            }
        }

        public int Count
        {
            get
            {
                int cpt = 0;
                foreach (A _data in lst)
                {
                    if (_data.State != OrmDataElementState.Deleted && _data.State != OrmDataElementState.ToDelete)
                    {
                        cpt++;
                    }
                }
                return cpt;
            }
        }

        public bool HasChanged
        {
            get
            {
                foreach (A _data in lst)
                {
                    if (_data.State != OrmDataElementState.Deleted && _data.State != OrmDataElementState.None)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        public bool IsEmpty
        {
            get
            {
                foreach (A _data in lst)
                {
                    if (_data.State != OrmDataElementState.Deleted && _data.State != OrmDataElementState.ToDelete)
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        public OrmDataElementListState ListState
        {
            get { return _state; }
            internal set { _state = value; }
        }

        #region IEnumerable<A> Members

        public IEnumerator<A> GetEnumerator()
        {
            foreach (A _data in lst)
            {
                if (_data.State != OrmDataElementState.Deleted && _data.State != OrmDataElementState.ToDelete)
                {
                    yield return _data;
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IListSource Members

        public bool ContainsListCollection
        {
            get { return false; }
        }

        public IList GetList()
        {
            List<A> lst = new List<A>(Count);
            foreach (A _data in this)
            {
                if (_data.State != OrmDataElementState.Deleted && _data.State != OrmDataElementState.ToDelete)
                {
                    lst.Add(_data);
                }
            }
            return lst;
        }

        #endregion

        internal void AddToList(A el)
        {
            el.SetCurrentValuesToOriginals();
            el.FieldChanging +=
                delegate(OrmDataElementBase<A> sender, OrmFieldBase<A> value)
                    {
                        OnFieldChanging(sender, value);
                    };
            el.FieldChanged +=
                delegate(OrmDataElementBase<A> sender, OrmFieldBase<A> value)
                    {
                        ListState = OrmDataElementListState.Changed;
                        OnFieldChanged(sender, value);
                    };
            lst.Add(el);
        }

        public void Insert(A el)
        {
            if (lst.Contains(el))
            {
                return;
            }
            //Permet de connaitre la liste des OrmFields
            foreach (PropertyInfo info in el.GetType().GetProperties())
            {
                if (info.PropertyType.Name.StartsWith("OrmValues"))
                {
                    info.GetValue(el, null);
                }
            }
            el.State = OrmDataElementState.ToInsert;
            el.DefaultFieldToString = _defaultFieldToString;
            ListState = OrmDataElementListState.Changed;
            lst.Add(el);
        }

        public void Delete(A el)
        {
            el.State = OrmDataElementState.ToDelete;
            ListState = OrmDataElementListState.Changed;
        }

        public void DeleteAll()
        {
            foreach (A a in this)
            {
                a.State = OrmDataElementState.ToDelete;
            }
        }

        public void SaveChanges()
        {
            SaveChanges(OrmDataElementOperation.Insert | OrmDataElementOperation.Update | OrmDataElementOperation.Delete);
        }

        public void SaveChanges(OrmDataElementOperation operations)
        {
            if (ListState == OrmDataElementListState.NoChanged)
            {
                return;
            }
            foreach (A a in lst)
            {
                if (a.State == OrmDataElementState.None)
                {
                    continue;
                }

                if (a.State == OrmDataElementState.ToInsert && (operations & OrmDataElementOperation.Insert) == OrmDataElementOperation.Insert)
                {
                    SaveChangeToInsert(a);
                }
                else if (a.State == OrmDataElementState.ToUpdate && (operations & OrmDataElementOperation.Update) == OrmDataElementOperation.Update)
                {
                    SaveChangeToUpdate(a);
                }
                else if (a.State == OrmDataElementState.ToDelete && (operations & OrmDataElementOperation.Delete) == OrmDataElementOperation.Delete)
                {
                    SaveChangeToDelete(a);
                    a.State = OrmDataElementState.Deleted;
                    continue;
                }
                a.SetCurrentValuesToOriginals();
                a.State = OrmDataElementState.None;
            }
            ListState = OrmDataElementListState.NoChanged;
        }

        public event OrmDataOperatingEventHandler OrmDataElementInserting;
        public event OrmDataOperatingEventHandler OrmDataElementUpdating;
        public event OrmDataOperatingEventHandler OrmDataElementDeleting;
        public event OrmDataElementSaveErrorEventHandler OrmDataElementSaveError;
        public event OrmDataElementBase<A>.FieldChangingEventHandler OrmDataElementFieldChanging;
        public event OrmDataElementBase<A>.FieldChangedEventHandler OrmDataElementFieldChanged;

        private void OnOrmDataElementInserting(OrmDataElementOperatingEventArgs<A> arg)
        {
            if (OrmDataElementInserting != null)
            {
                OrmDataElementInserting(this, arg);
            }
        }

        private void OnOrmDataElementUpdating(OrmDataElementOperatingEventArgs<A> arg)
        {
            if (OrmDataElementUpdating != null)
            {
                OrmDataElementUpdating(this, arg);
            }
        }

        private void OnOrmDataElementDeleting(OrmDataElementOperatingEventArgs<A> arg)
        {
            if (OrmDataElementDeleting != null)
            {
                OrmDataElementDeleting(this, arg);
            }
        }

        private void SaveChangeToInsert(A el)
        {
            OrmDataElementOperatingEventArgs<A> arg = new OrmDataElementOperatingEventArgs<A>(el, OrmDataElementState.ToInsert);
            OnOrmDataElementInserting(arg);
            if (!arg.Cancel)
            {
                try
                {
                    SqlHelper.ExecQueryNonQuery(_strConnectionName, OrmQueryFactory.GetInsertQuery(el));
                }
                catch (Exception ex)
                {
                    OnOrmDataSaveError(new OrmDataElementSaveErrorEventArgs<A>(el, OrmDataElementOperation.Insert, ex));
                }
            }
        }

        private void SaveChangeToUpdate(A el)
        {
            OrmDataElementOperatingEventArgs<A> arg = new OrmDataElementOperatingEventArgs<A>(el, OrmDataElementState.ToUpdate);
            OnOrmDataElementUpdating(arg);
            if (!arg.Cancel)
            {
                try
                {
                    SqlHelper.ExecQueryNonQuery(_strConnectionName, OrmQueryFactory.GetUpdateQuery(el));
                }
                catch (Exception ex)
                {
                    OnOrmDataSaveError(new OrmDataElementSaveErrorEventArgs<A>(el, OrmDataElementOperation.Update, ex));
                }
            }
        }

        private void SaveChangeToDelete(A el)
        {
            OrmDataElementOperatingEventArgs<A> arg = new OrmDataElementOperatingEventArgs<A>(el, OrmDataElementState.ToDelete);
            OnOrmDataElementDeleting(arg);
            if (!arg.Cancel)
            {
                try
                {
                    SqlHelper.ExecQueryNonQuery(_strConnectionName, OrmQueryFactory.GetDeleteQuery(el));
                }
                catch (Exception ex)
                {
                    OnOrmDataSaveError(new OrmDataElementSaveErrorEventArgs<A>(el, OrmDataElementOperation.Delete, ex));
                }
            }
        }

        public void OnOrmDataSaveError(OrmDataElementSaveErrorEventArgs<A> arg)
        {
            if (OrmDataElementSaveError != null)
            {
                OrmDataElementSaveError(this, arg);
            }
        }

        private void OnFieldChanging(OrmDataElementBase<A> sender, OrmFieldBase<A> field)
        {
            if (OrmDataElementFieldChanging != null)
            {
                OrmDataElementFieldChanging(sender, field);
            }
        }

        private void OnFieldChanged(OrmDataElementBase<A> sender, OrmFieldBase<A> field)
        {
            if (OrmDataElementFieldChanged != null)
            {
                OrmDataElementFieldChanged(sender, field);
            }
        }

        private OrmFieldBase<A> _defaultFieldToString;

        public OrmFieldBase<A> DefaultFieldToString
        {
            set
            {
                _defaultFieldToString = value;
                foreach (A a in lst)
                {
                    a.DefaultFieldToString = value;
                }
            }
        }

        public OrmDataElementBase<A> GetMax(OrmFieldBase<A> field)
        {
            A _a = null;
            foreach (A a in this)
            {
                if (_a == null)
                    _a = a;
                else if (-1 == string.Compare(field.GetValues(_a)._currentValue.ToString(), field.GetValues(a)._currentValue.ToString()))
                    _a = a;
            }
            return _a;
        }

        public OrmDataElementBase<A> GetMin(OrmFieldBase<A> field)
        {
            A _a = null;
            foreach (A a in this)
            {
                if (_a == null)
                    _a = a;
                else if (1 == string.Compare(field.GetValues(_a)._currentValue.ToString(), field.GetValues(a)._currentValue.ToString()))
                    _a = a;
            }
            return _a;
        }
    }
}